#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif
#include <stdio.h>
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include <include/pf_errno.h>
#include <include/pf_internal.h>
#include <include/pf_str.h>

#define FIND_UNIT(x, y, z) if((num & (~0ULL << x)) == 0){y;}else{z;}

/*********************************************************************************************
Function Name: best_unit
Description  : Choose best unit according input number
Inputs       : int64_t num                        : number
Outputs      : return value                       : 'E' when number > 2^60
                                                    'P' when number > 2^50
                                                    'T' when number > 2^40
                                                    'G' when number > 2^30
                                                    'M' when number > 2^20
                                                    'K' when number > 2^10
                                                    'B' when number < 2^10
ErrorCodes   : no
History      :
---------------------------------------------------------------------------------------------
Author                 Date                          Comments
lanqj                  2011-6-24                     create
**********************************************************************************************/
static inline char best_unit(uint64_t num);

/*********************************************************************************************
Function Name: unit_2_num
Description  : Convert unit to number
Inputs       : char unit                 : input number unit
Outputs      : return value              : 2^10 when unit is 'k' or 'K'
                                           2^20 when unit is 'm' or 'M'
                                           2^30 when unit is 'g' or 'G'
                                           2^40 when unit is 't' or 'T'
                                           2^50 when unit is 'p' or 'P'
                                           2^60 when unit is 'e' or 'E'
                                           <  0 Fail when unit is no the characer like above
ErrorCodes   : no
History      :
---------------------------------------------------------------------------------------------
Author                 Date                          Comments
lanqj                  2011-6-24                     create
**********************************************************************************************/
static inline int64_t unit_2_num(char unit);

/*********************************************************************************************
Function Name: char_2_int
Description  : Convert character to integer
Inputs       : int32_t c                 : input character
Outputs      : return value              : >= 0 integer
                                         : <  0 Fail
ErrorCodes   : no
History      :
---------------------------------------------------------------------------------------------
Author                 Date                          Comments
lanqj                  2011-6-24                     create
**********************************************************************************************/
static inline int32_t char_2_int(int32_t c);

/********************************************************************************************/

int32_t pf_str_2_hex(void* bin, int32_t bin_size, const char* str)
{
    union
    {
        struct
        {
#if WORDS_BIGENDIAN
            uint8_t high    : 4;
            uint8_t low     : 4;
#else
            uint8_t low     : 4;
            uint8_t high    : 4;
#endif
        } __attribute__((packed)) sep;
        uint8_t value;
    } __attribute__((packed)) helper;

    int32_t t             = 0;
    int32_t i             = 0;
    const char* p         = str;
    unsigned char* output = (unsigned char*)bin;

    assert(str != NULL && output != NULL);

    if(bin_size <= 0 || *str == '\0')
    {
        return PF_EINVAL;
    }

    while((*p != '\0') && i < bin_size)
    {
        t = char_2_int(*p++);
        if(t < 0)
        {
            return PF_EINVAL;
        }
        helper.sep.high = t;

        t = char_2_int(*p++);
        if(t < 0)
        {
            return PF_EINVAL;
        }
        helper.sep.low = t;

        output[i++] = helper.value;
    }

    return 0;
}

int32_t pf_hex_2_str(char* str,
                     int32_t str_len,
                     const void* bin,
                     int32_t bin_size,
                     int32_t lowercase)
{
    int32_t i = 0;
    const char* output = (lowercase == 1) ? "0123456789abcdef" : "0123456789ABCDEF";
    const unsigned char* input = (const unsigned char*)bin;

    assert(str != NULL && bin != NULL);

    /* one byte hex buffer need 2 bytes string buffer to store,
       the last byte used to store '\0' */
    if(bin_size <=0 || (str_len) < (bin_size * 2 + 1))
    {
        return PF_EINVAL;
    }

    for(i = 0;i < bin_size;i++)
    {
        *str++ = output[(input[i] & 0xF0) >> 4]; /* convert high 4 bit to string */
        *str++ = output[input[i] & 0x0F];        /* convert low 4 bit to string */
    }
    *str = '\0';

    return 0;
}

int32_t pf_int_2_str(char* str,
                     int32_t str_len,
                     int64_t input,
                     int32_t lowercase,
                     int32_t base)
{
    int32_t i      = 0;
    int32_t sign   = 0;
    int32_t length = 0;
    uint64_t value = 0;
    char* sp       = NULL;
    char tmp[72]   = {'\0'};
    char* tp       = tmp + 70;
    char a         = (lowercase != 0) ? 'a' : 'A';

    assert(str != NULL);

    if(base > 36 || base < 0 || base == 1 || str_len <= 0)
    {
        return PF_EINVAL;
    }

    if(base == 0)
    {
        base = 10;
    }

    sign = ((base != 2 && base != 4 && base != 8 && base != 16 && base != 32) 
            && input < 0);

    if(sign)
    {
        value = -input;
    }
    else
    {
        value = (uint64_t)input;
    }
    while(value || tp >= tmp)
    {
        i = value % base;      /* get the unit */
        value = value / base;  /* calculate the rest value for next time's process */
        if(i < 10)
        {
            *tp-- = i+'0';
        }
        else
        {
            *tp-- = i + a - 10;
        }
        if(value == 0)
        {
            break;
        }
    }

    sp = str;

    /* if is a nagtive number, then add '-' at the beging of the string buffer */
    if(sign && str_len--)
    {
        *sp++ = '-';
    }

    length = 71 - (tp - tmp); /* include '\0' */

    if(str_len < length)
    {
        return PF_EINVAL; 
    }
    else
    {
        memcpy(sp,tp+1,length);
        return 0; 
    }
}

int32_t pf_str_2_int32(int32_t *output, const char* str, int32_t base)
{
    char* end_ptr = NULL;

    assert(output != NULL && str != NULL);

    errno = 0;
    (*output) = (int32_t)strtol(str,&end_ptr,base);
    if(errno != 0)
    {
        return pf_errno();
    }
    else if(*end_ptr != '\0')
    {
        return PF_EINVAL;
    }
    else
    {
        return 0;
    }
}

int32_t pf_str_2_int64(int64_t* output, const char* str, int32_t base)
{
    char* end_ptr = NULL;

    assert(output != NULL && str != NULL);

    errno = 0;
    (*output) = (int64_t)strtoll(str, &end_ptr, base);
    if(errno != 0)
    {
        return pf_errno();
    }
    else if(*end_ptr != '\0')
    {
        return PF_EINVAL;
    }
    else
    {
        return 0;
    }
}

char* pf_str_trim(char* str, const char* white_space_chars)
{
    int32_t w_length = 0;
    int32_t s_length = 0;
    char* beg_ptr    = NULL;
    char* end_ptr    = NULL;
    char* c_pos      = NULL;

    assert(str!=NULL && white_space_chars != NULL);

    w_length = strlen(white_space_chars);
    s_length = strlen(str);
    beg_ptr  = str;
    end_ptr  = str+ s_length - 1;

    if(w_length == 0 || s_length == 0)  /* invalid parameter */
    {
        return str;
    }

    /* trim the tail white_space_chars */
    do
    {
        c_pos = strchr(white_space_chars, *end_ptr);
    }
    while((c_pos != NULL) && (end_ptr-- > beg_ptr));
    *(end_ptr+1) = '\0';

    /* trim the head white_space_chars */
    if(beg_ptr < (end_ptr + 1)) /* go to the begin point after triming from the tail */
    {
        c_pos = NULL;
        do
        {
            c_pos = strchr(white_space_chars, *beg_ptr);
        }
        while((c_pos != NULL) && (beg_ptr++ <= (end_ptr + 1)));
    }

    return beg_ptr;
}

int32_t pf_str_2_int_with_unit(int64_t *output, const char* str)
{
    long double num  = 0;
    char* end_ptr    = NULL;
    int64_t unit_num = 1;

    assert(output != NULL && str != NULL);

    errno = 0;
    num = strtold(str, &end_ptr);
    if(errno != 0)  
    {
        return pf_errno();
    }
    else if(*end_ptr != '\0' && end_ptr != str && *(end_ptr+1) == '\0') 
    {   /* *end_ptr = unit, like 10.8K,unit==k */
        unit_num = unit_2_num(*end_ptr);
        if(unit_num < 0)  /* unit is invalid */
        {
            return PF_EINVAL;
        }
    }
    else if(*end_ptr == '\0' && end_ptr != str) /* no unit */
    {
        unit_num = 1;
    }
    else /*it's invalid string like "K" or "10.kbc" */
    {
        return PF_EINVAL;
    }

    num *= unit_num;
    *output = (int64_t)num;

    return 0;
}

int32_t pf_int_2_str_with_unit(char* str,
                               int32_t str_len,
                               int64_t number,
                               char unit)
{
    double result   = 0.0;
    int32_t n       = 0;
    int32_t sign    = 0;
    uint64_t num    = (uint64_t)number;
    int64_t basenum = 0;

    assert(str != NULL);

    if(str_len <= 0)
    {
        return PF_EINVAL;
    }

    if(number < 0)
    {
        sign = 1;
        num = -number;
    }

    if(unit == 0) /* choose the best unit automatically */
    {
        unit = best_unit(num);
    }

    if((basenum = unit_2_num(unit)) < 0)
    {
        return PF_EINVAL;
    }

    result = (double)num / basenum; /* get the integral result of the unit */

    if(sign)
    {
        /* the last byte used to store '\0' in the extreme env. */
        n = snprintf(str, str_len - 1, "-%0.2f", result);
    }
    else
    {
        n = snprintf(str,str_len - 1, "%0.2f", result);
    }

    if((n + 1) > str_len) /* str_len must >= n+1 */
    {
        return PF_EINVAL;
    }
    else
    {
        str[n] = unit;   /* converse '\0' to unit */
        str[n+1] = '\0'; /* append '\0' */
    }

    return 0;
}

/********************************************************************************************/

static inline char best_unit(uint64_t num)
{
    FIND_UNIT(20, FIND_UNIT(10, return 'B', return 'K'), 
              FIND_UNIT(40, FIND_UNIT(30, return 'M', return 'G'), 
              FIND_UNIT(50, return 'T', return (num > (1ULL << 60)) ? 'E' : 'P')));

}

static inline int64_t unit_2_num(char unit)
{
    int64_t num = 1;

    switch(unit)
    {
        case 'b':
        case 'B':
            return (int64_t)1;
        case 'k':
        case 'K':
            return num << 10;
        case 'm':
        case 'M':
            return num << 20;
        case 'g':
        case 'G':
            return num << 30;
        case 't':
        case 'T':
            return num << 40;
        case 'p':
        case 'P':
            return num << 50;
        case 'e':
        case 'E':
            return num << 60;
        default:
            return (int64_t)-1;
    }
}

static inline int32_t char_2_int(int32_t c)
{
    switch(c)
    {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            return c - '0';
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
            return c - 'A' + 10;
        case 'a':
        case 'b':
        case 'c':
        case 'd':
        case 'e':
        case 'f':
            return c - 'a' + 10;
        default:
            return -1;
    }
}
